﻿using System;
using System.Linq;
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
using MyBasicLib.Reflection;
using MyBasicLib.Reflection.FastReflection.Emit;

namespace MyBasicLib.Extensions
{
    public static class ExpressionExtension
    {
        public static Expression TrimUnary(this Expression expression)
        {
            var expression1 = expression as UnaryExpression;
            return expression1 != null ? TrimUnary(expression1.Operand) : expression;
        } 
        private static readonly MethodInvoker ArrayIndexMethodInvoker = new MethodInvoker(typeof(IList).GetProperty("Item").GetGetMethod());

        public static object Eval(this Expression exp)
        {
            if (exp == null) return null;
            exp = exp.TrimUnary();
            if (exp.NodeType == ExpressionType.Constant)
            {
                return ((ConstantExpression)exp).Value;
            }
            if (exp.NodeType == ExpressionType.MemberAccess)
            {
                var memEp = exp as MemberExpression;
                MemberInfo mi = memEp.Member;
                object owner = Eval(memEp.Expression);
                var pi = mi as PropertyInfo;
                if (pi != null)
                {
                    return pi.FastGetValue(owner);
                }
                return  (mi as FieldInfo).FastGetValue(owner);
            }
            if (exp.NodeType == ExpressionType.Call)
            {
                var callEp = exp as MethodCallExpression;
                MethodInfo mi = callEp.Method;
                object[] args = callEp.Arguments.Select(p => p.Eval()).ToArray();
                return  mi.FastInvoke(callEp.Object.Eval(), args);              
            }
            if (exp.NodeType == ExpressionType.ArrayIndex)
            {
                var smp = exp as BinaryExpression;
                object arr = smp.Left.Eval();
                object idx = smp.Right.Eval();
                return ArrayIndexMethodInvoker.Invoke(arr, idx);
            }
            throw new NotSupportedException();
            //LambdaExpression lambda = Expression.Lambda(exp);
            //Delegate fn = lambda.Compile();

            //return fn.DynamicInvoke(null);
        }
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1,
                                                       Expression<Func<T, bool>> expression2)
        {
            BinaryExpression exp = Expression.And(((LambdaExpression) expression1.TrimUnary()).Body,
                                                  ((LambdaExpression) expression2.TrimUnary()).Body);
            return Expression.Lambda<Func<T, bool>>(exp, expression1.Parameters); // exp as Expression<Func<T, bool>>;
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1,
                                                      Expression<Func<T, bool>> expression2)
        {
            BinaryExpression exp = Expression.Or(((LambdaExpression) expression1.TrimUnary()).Body,
                                                 ((LambdaExpression) expression2.TrimUnary()).Body);
            return Expression.Lambda<Func<T, bool>>(exp, expression1.Parameters); // exp as Expression<Func<T, bool>>;
        }
    }
}